using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

namespace CloudShot.Utils
{
  /// <summary>
  /// Provides functions to capture the entire screen, or a particular window, and save it to a file.
  /// </summary>
  public static class ScreenCapture
  {
    /// <summary>
    /// Creates an Image object containing a screen shot of the entire desktop
    /// </summary>
    /// <returns></returns>
    public static Image CaptureScreen()
    {
      return CaptureWindow(User32.GetDesktopWindow());
    }

    /// <summary>
    /// Creates an Image object containing a screen shot of a specific window
    /// </summary>
    /// <param name="handle">The handle to the window. (In windows forms, this is obtained by the Handle property)</param>
    /// <returns></returns>
    public static Image CaptureWindow(IntPtr handle)
    {
      // get te hDC of the target window
      IntPtr hdcSrc = User32.GetWindowDC(handle);
      // get the size
      User32.RECT windowRect = new User32.RECT();
      User32.GetWindowRect(handle, ref windowRect);
      int width = windowRect.Right - windowRect.Left;
      int height = windowRect.Bottom - windowRect.Top;
      // create a device context we can copy to
      IntPtr hdcDest = Gdi32.CreateCompatibleDC(hdcSrc);
      // create a bitmap we can copy it to,
      // using GetDeviceCaps to get the width/height
      IntPtr hBitmap = Gdi32.CreateCompatibleBitmap(hdcSrc, width, height);
      // select the bitmap object
      IntPtr hOld = Gdi32.SelectObject(hdcDest, hBitmap);
      // bitblt over
      Gdi32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, 0, 0, Gdi32.SRCCOPY);
      // restore selection
      Gdi32.SelectObject(hdcDest, hOld);
      // clean up 
      Gdi32.DeleteDC(hdcDest);
      User32.ReleaseDC(handle, hdcSrc);

      // get a .NET image object for it
      Image img = Image.FromHbitmap(hBitmap);
      // free up the Bitmap object
      Gdi32.DeleteObject(hBitmap);

      return img;
    }

    public static Image CaptureWindowClientArea(IntPtr handle)
    {
      // get te hDC of the target window
      IntPtr hdcSrc = User32.GetWindowDC(handle);
      // get the size
      User32.RECT windowRect = new User32.RECT();
      User32.GetWindowRect(handle, ref windowRect);
      int width = windowRect.Right - windowRect.Left;
      int height = windowRect.Bottom - windowRect.Top;
      // create a device context we can copy to
      IntPtr hdcDest = Gdi32.CreateCompatibleDC(hdcSrc);
      // create a bitmap we can copy it to,
      // using GetDeviceCaps to get the width/height
      IntPtr hBitmap = Gdi32.CreateCompatibleBitmap(hdcSrc, width, height);
      // select the bitmap object
      IntPtr hOld = Gdi32.SelectObject(hdcDest, hBitmap);
      // bitblt over
      User32.PrintWindow(handle, hdcDest, User32.PW_CLIENTONLY); //does the shot of window even if it is covered by another window
      // restore selection
      Gdi32.SelectObject(hdcDest, hOld);
      // clean up 
      Gdi32.DeleteDC(hdcDest);
      User32.ReleaseDC(handle, hdcSrc);

      // get a .NET image object for it
      Image img = Image.FromHbitmap(hBitmap);
      // free up the Bitmap object
      Gdi32.DeleteObject(hBitmap);

      return img;
    }

    public static Bitmap CaptureRectangle(Rectangle windowRect)
    {
      if ((windowRect.Height <= 0) || windowRect.Width <= 0)
        return null; //throw new ArgumentException("The Rectangle has no size", "windowRect");

      IntPtr handle = User32.GetDesktopWindow();

      // get te hDC of the target window
      IntPtr hdcSrc = User32.GetWindowDC(handle);
      // get the size
      //User32.RECT windowRect = new User32.RECT();
      //User32.GetWindowRect(handle, ref windowRect);
      //int width = windowRect.Right - windowRect.Left;
      //int height = windowRect.Bottom - windowRect.Top;
      // create a device context we can copy to
      IntPtr hdcDest = Gdi32.CreateCompatibleDC(hdcSrc);
      // create a bitmap we can copy it to,
      // using GetDeviceCaps to get the width/height
      IntPtr hBitmap = Gdi32.CreateCompatibleBitmap(hdcSrc, windowRect.Width, windowRect.Height);
      // select the bitmap object
      IntPtr hOld = Gdi32.SelectObject(hdcDest, hBitmap);
      // bitblt over
      Gdi32.BitBlt(hdcDest, 0, 0, windowRect.Width, windowRect.Height, hdcSrc, windowRect.Left, windowRect.Top, Gdi32.SRCCOPY);
      // restore selection
      Gdi32.SelectObject(hdcDest, hOld);
      // clean up 
      Gdi32.DeleteDC(hdcDest);
      User32.ReleaseDC(handle, hdcSrc);

      // get a .NET image object for it
      Bitmap img = Image.FromHbitmap(hBitmap);
      // free up the Bitmap object
      Gdi32.DeleteObject(hBitmap);

      return img;
    }

    /// <summary>
    /// Captures a screen shot of a specific window, and saves it to a file
    /// </summary>
    /// <param name="handle"></param>
    /// <param name="filename"></param>
    /// <param name="format"></param>
    public static void CaptureWindowToFile(IntPtr handle, string filename, ImageFormat format)
    {
      Image img = CaptureWindow(handle);
      img.Save(filename, format);
    }

    /// <summary>
    /// Captures a screen shot of the entire desktop, and saves it to a file
    /// </summary>
    /// <param name="filename"></param>
    /// <param name="format"></param>
    public static void CaptureScreenToFile(string filename, ImageFormat format)
    {
      Image img = CaptureScreen();
      img.Save(filename, format);
    }

    public static void CaptureRectangleToFile(Rectangle rect, string filename, ImageFormat format)
    {
      Image img = CaptureRectangle(rect);
      img.Save(filename, format);
    }

    private static Bitmap CaptureCursor(ref int x, ref int y)
    {
      //http://stackoverflow.com/questions/918990/c-sharp-capturing-the-mouse-cursor-image

      User32.CURSORINFO cursorInfo = new User32.CURSORINFO();
      cursorInfo.cbSize = Marshal.SizeOf(cursorInfo);
      if (!User32.GetCursorInfo(out cursorInfo))
        return null;

      if (cursorInfo.flags != User32.CURSOR_SHOWING)
        return null;

      IntPtr hicon = User32.CopyIcon(cursorInfo.hCursor);
      if (hicon == IntPtr.Zero)
        return null;

      User32.ICONINFO iconInfo;
      if (!User32.GetIconInfo(hicon, out iconInfo))
        return null;

      x = cursorInfo.ptScreenPos.x - ((int)iconInfo.xHotspot);
      y = cursorInfo.ptScreenPos.y - ((int)iconInfo.yHotspot);

      using (Bitmap maskBitmap = Bitmap.FromHbitmap(iconInfo.hbmMask))
      {
        // Is this a monochrome cursor?
        if (maskBitmap.Height == maskBitmap.Width * 2)
        {
          Bitmap resultBitmap = new Bitmap(maskBitmap.Width, maskBitmap.Width);

          Graphics desktopGraphics = Graphics.FromHwnd(User32.GetDesktopWindow());
          IntPtr desktopHdc = desktopGraphics.GetHdc();

          IntPtr maskHdc = Gdi32.CreateCompatibleDC(desktopHdc);
          IntPtr oldPtr = Gdi32.SelectObject(maskHdc, maskBitmap.GetHbitmap());

          using (Graphics resultGraphics = Graphics.FromImage(resultBitmap))
          {
            IntPtr resultHdc = resultGraphics.GetHdc();

            // These two operation will result in a black cursor over a white background.
            // Later in the code, a call to MakeTransparent() will get rid of the white background.
            Gdi32.BitBlt(resultHdc, 0, 0, 32, 32, maskHdc, 0, 32, Gdi32.TernaryRasterOperations.SRCCOPY);
            Gdi32.BitBlt(resultHdc, 0, 0, 32, 32, maskHdc, 0, 0, Gdi32.TernaryRasterOperations.SRCINVERT);

            resultGraphics.ReleaseHdc(resultHdc);
          }

          IntPtr newPtr = Gdi32.SelectObject(maskHdc, oldPtr);
          Gdi32.DeleteDC(newPtr);
          Gdi32.DeleteDC(maskHdc);
          desktopGraphics.ReleaseHdc(desktopHdc);

          // Remove the white background from the BitBlt calls,
          // resulting in a black cursor over a transparent background.
          resultBitmap.MakeTransparent(Color.White);
          return resultBitmap;
        }
      }

      Icon icon = Icon.FromHandle(hicon);
      return icon.ToBitmap();
    }

    public static void DrawCurrentCursor(this Graphics graphics, Point imageOffset = new Point())
    {
      int x = 0;
      int y = 0;

      Bitmap iconBitmap = CaptureCursor(ref x, ref y);

      if (iconBitmap != null)
      {
        var clientCursorPosition = new Point(x - imageOffset.X, y - imageOffset.Y);
        graphics.DrawImage(iconBitmap, clientCursorPosition.X, clientCursorPosition.Y);
      }
    }
  }
}